package com.gitee.wsl.ext.number

import com.gitee.wsl.io.writeMagnitudeLong
import kotlinx.io.Buffer
import kotlinx.io.readByteArray

/**
 * Encode as a four-byte array
 */
fun Int.encodeTo4Bytes(): ByteArray = byteArrayOf(
    (this ushr 24).toByte(),
    (this ushr 16).toByte(),
    (this ushr 8).toByte(),
    (this).toByte()
)

/**
 * Encode as an eight-byte array
 */
fun Long.encodeTo8Bytes(): ByteArray = byteArrayOf(
    (this ushr 56).toByte(),
    (this ushr 48).toByte(),
    (this ushr 40).toByte(),
    (this ushr 32).toByte(),
    (this ushr 24).toByte(),
    (this ushr 16).toByte(),
    (this ushr 8).toByte(),
    (this).toByte()
)

/** Encodes an unsigned Long to a minimum-size twos-complement byte array */
fun ULong.toTwosComplementByteArray() = when {
    this >= 0x8000000000000000UL ->
        byteArrayOf(
            0x00,
            (this shr 56).toByte(),
            (this shr 48).toByte(),
            (this shr 40).toByte(),
            (this shr 32).toByte(),
            (this shr 24).toByte(),
            (this shr 16).toByte(),
            (this shr 8).toByte(),
            this.toByte()
        )

    else -> this.toLong().toTwosComplementByteArray()
}

/** Encodes an unsigned Int to a minimum-size twos-complement byte array */
fun UInt.toTwosComplementByteArray() = toLong().toTwosComplementByteArray()

/** Encodes a signed Long to a minimum-size twos-complement byte array */
fun Long.toTwosComplementByteArray() = when {
    (this >= -0x80L && this <= 0x7FL) ->
        byteArrayOf(
            this.toByte()
        )

    (this >= -0x8000L && this <= 0x7FFFL) ->
        byteArrayOf(
            (this ushr 8).toByte(),
            this.toByte()
        )

    (this >= -0x800000L && this <= 0x7FFFFFL) ->
        byteArrayOf(
            (this ushr 16).toByte(),
            (this ushr 8).toByte(),
            this.toByte()
        )

    (this >= -0x80000000L && this <= 0x7FFFFFFFL) ->
        byteArrayOf(
            (this ushr 24).toByte(),
            (this ushr 16).toByte(),
            (this ushr 8).toByte(),
            this.toByte()
        )

    (this >= -0x8000000000L && this <= 0x7FFFFFFFFFL) ->
        byteArrayOf(
            (this ushr 32).toByte(),
            (this ushr 24).toByte(),
            (this ushr 16).toByte(),
            (this ushr 8).toByte(),
            this.toByte()
        )

    (this >= -0x800000000000L && this <= 0x7FFFFFFFFFFFL) ->
        byteArrayOf(
            (this ushr 40).toByte(),
            (this ushr 32).toByte(),
            (this ushr 24).toByte(),
            (this ushr 16).toByte(),
            (this ushr 8).toByte(),
            this.toByte()
        )

    (this >= -0x80000000000000L && this <= 0x7FFFFFFFFFFFFFL) ->
        byteArrayOf(
            (this ushr 48).toByte(),
            (this ushr 40).toByte(),
            (this ushr 32).toByte(),
            (this ushr 24).toByte(),
            (this ushr 16).toByte(),
            (this ushr 8).toByte(),
            this.toByte()
        )

    else ->
        byteArrayOf(
            (this ushr 56).toByte(),
            (this ushr 48).toByte(),
            (this ushr 40).toByte(),
            (this ushr 32).toByte(),
            (this ushr 24).toByte(),
            (this ushr 16).toByte(),
            (this ushr 8).toByte(),
            this.toByte()
        )
}

/** Encodes a signed Int to a minimum-size twos-complement byte array */
fun Int.toTwosComplementByteArray() = toLong().toTwosComplementByteArray()

fun Int.Companion.fromTwosComplementByteArray(it: ByteArray) = when (it.size) {
    4 -> it[0].shiftLeftFirstInt(24) or
            (it[1] shiftLeftAsInt 16) or
            (it[2] shiftLeftAsInt 8) or
            (it[3] shiftLeftAsInt 0)

    3 -> it[0].shiftLeftFirstInt(16) or
            (it[1] shiftLeftAsInt 8) or
            (it[2] shiftLeftAsInt 0)

    2 -> it[0].shiftLeftFirstInt(8) or
            (it[1] shiftLeftAsInt 0)

    1 -> it[0].shiftLeftFirstInt(0)
    else -> throw IllegalArgumentException("Input with size $it is out of bounds for Int")
}

private infix fun Byte.shiftLeftAsInt(shift: Int) = this.toUByte().toInt() shl shift
private fun Byte.shiftLeftFirstInt(shift: Int) = toInt() shl shift

fun UInt.Companion.fromTwosComplementByteArray(it: ByteArray) =
    Long.fromTwosComplementByteArray(it).let {
        require((0 <= it) && (it <= 0xFFFFFFFFL)) { "Value $it is out of bounds for UInt" }
        it.toUInt()
    }

fun Long.Companion.fromTwosComplementByteArray(it: ByteArray) = when (it.size) {
    8 -> it[0].shiftLeftFirstLong(56) or
            (it[1] shiftLeftAsLong 48) or
            (it[2] shiftLeftAsLong 40) or
            (it[3] shiftLeftAsLong 32) or
            (it[4] shiftLeftAsLong 24) or
            (it[5] shiftLeftAsLong 16) or
            (it[6] shiftLeftAsLong 8) or
            (it[7] shiftLeftAsLong 0)

    7 -> it[0].shiftLeftFirstLong(48) or
            (it[1] shiftLeftAsLong 40) or
            (it[2] shiftLeftAsLong 32) or
            (it[3] shiftLeftAsLong 24) or
            (it[4] shiftLeftAsLong 16) or
            (it[5] shiftLeftAsLong 8) or
            (it[6] shiftLeftAsLong 0)

    6 -> it[0].shiftLeftFirstLong(40) or
            (it[1] shiftLeftAsLong 32) or
            (it[2] shiftLeftAsLong 24) or
            (it[3] shiftLeftAsLong 16) or
            (it[4] shiftLeftAsLong 8) or
            (it[5] shiftLeftAsLong 0)

    5 -> it[0].shiftLeftFirstLong(32) or
            (it[1] shiftLeftAsLong 24) or
            (it[2] shiftLeftAsLong 16) or
            (it[3] shiftLeftAsLong 8) or
            (it[4] shiftLeftAsLong 0)

    4 -> it[0].shiftLeftFirstLong(24) or
            (it[1] shiftLeftAsLong 16) or
            (it[2] shiftLeftAsLong 8) or
            (it[3] shiftLeftAsLong 0)

    3 -> it[0].shiftLeftFirstLong(16) or
            (it[1] shiftLeftAsLong 8) or
            (it[2] shiftLeftAsLong 0)

    2 -> it[0].shiftLeftFirstLong(8) or
            (it[1] shiftLeftAsLong 0)

    1 -> it[0].shiftLeftFirstLong(0)
    else -> throw IllegalArgumentException("Input with size $it is out of bounds for Long")
}

private infix fun Byte.shiftLeftAsLong(shift: Int) = this.toUByte().toLong() shl shift
private fun Byte.shiftLeftFirstLong(shift: Int) = toLong() shl shift

fun ULong.Companion.fromTwosComplementByteArray(it: ByteArray) = when {
    ((it.size == 9) && (it[0] == 0.toByte())) -> it.shiftLeftAsULong(1, 56) or
            it.shiftLeftAsULong(2, 48) or
            it.shiftLeftAsULong(3, 40) or
            it.shiftLeftAsULong(4, 32) or
            it.shiftLeftAsULong(5, 24) or
            it.shiftLeftAsULong(6, 16) or
            it.shiftLeftAsULong(7, 8) or
            it.shiftLeftAsULong(8, 0)

    else -> Long.fromTwosComplementByteArray(it).let {
        require(it >= 0) { "Value $it is out of bounds for ULong" }
        it.toULong()
    }
} as ULong

private fun ByteArray.shiftLeftAsULong(index: Int, shift: Int) = this[index].toUByte().toULong() shl shift

/** Encodes an unsigned Long to a minimum-size unsigned byte array */
fun Long.toUnsignedByteArray(): ByteArray = throughBuffer { it.writeMagnitudeLong(this) }

/** Encodes an unsigned Int to a minimum-size unsigned byte array */
fun Int.toUnsignedByteArray() = toLong().toUnsignedByteArray()


private fun List<Byte>.isLastIndex(it: Int) = it == size - 1

private fun List<Byte>.fromBack(it: Int) = this[size - 1 - it]

/**
 * Helper to create a buffer, operate on it and return its contents as a [ByteArray]
 */
internal inline fun throughBuffer(operation: (Buffer) -> Unit): ByteArray =
    Buffer().also(operation).readByteArray()